#include <linux/security.h>

/* custom headers */
#include "ak_common.h"
#include "lsm_public.h"
#if 0
#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,16)
struct security_operations security_custom_ops = NULL;

int custom_security_register(void)
{
    memset(&security_custom_ops, 0x00, sizeof(struct security_operations));
    security_custom_ops.inode_permission = hook_inode_permission;
    security_custom_ops.inode_create = hook_inode_create;
    security_custom_ops.inode_mkdir = hook_inode_mkdir;
    security_custom_ops.inode_rmdir = hook_inode_rmdir;
    security_custom_ops.inode_link = hook_inode_link;
    security_custom_ops.inode_unlink = hook_inode_unlink;
    security_custom_ops.inode_rename = hook_inode_rename;
    security_custom_ops.inode_symlink = hook_inode_symlink;

    security_custom_ops.bprm_check_security = hook_bprm_check_security;
    security_custom_ops.file_permission = hook_file_permission;
    security_custom_ops.mmap_file = hook_mmap_file;

    return register_security(&security_custom_ops);
}

void custom_security_unregister(void)
{
    return unregister_security(&security_custom_ops);
}

#elif LINUX_VERSION_CODE > KERNEL_VERSION(2,6,16) && LINUX_VERSION_CODE < KERNEL_VERSION(4,2,0)

struct security_operations *security_custom_ops = NULL;
struct security_operations security_backup_ops;


#define reg_hook_func(custom_ops,funcname,backup_ops,prefix) do{ \
    backup_ops.funcname   = custom_ops->funcname;              \
    custom_ops->funcname  = prefix##funcname;            \
}while(0)

int custom_security_register(void)
{
    unsigned long cr0;
    ulong *paddr = NULL;
    ulong hook_address = 0x00UL;

#if (LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,24))
    hook_address = find_symbol_addr("security_ops");
#else
    struct kernsym  sy;
    find_symbol_address(&sy,"security_ops");
    hook_address = (ulong)sy.run;
#endif
    if(hook_address == 0x00UL || hook_address == (~0x00UL)){
        printk("get security hook address error\n");
        return -1;
    }
    
    paddr = (ulong *)hook_address;
    security_custom_ops =(struct security_operations *) (*paddr);
    if(security_custom_ops == NULL){
        printk("can't find lsm hook.\n");
        return -1;
    }
    
    cr0 = clear_and_return_cr0();

    reg_hook_func(security_custom_ops,inode_permission, security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_create,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_mkdir,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_rmdir,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_link,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_unlink,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_rename,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,inode_symlink,security_backup_ops,hook_);

    reg_hook_func(security_custom_ops,bprm_check_security,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,file_permission,security_backup_ops,hook_);
    reg_hook_func(security_custom_ops,mmap_file,security_backup_ops,hook_);
    
    setback_cr0(cr0);
    return 0;
}
#define unreg_hook_func(sec_ops, funcname, backup_ops) do{ \
    sec_ops->funcname = backup_ops.funcname;             \
}while(0)

void custom_security_unregister(void)
{
    unsigned long cr0;

    cr0 = clear_and_return_cr0();

    /* security inode */
    unreg_hook_func(security_custom_ops, inode_permission, security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_create,  security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_mkdir,   security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_rmdir,   security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_link,    security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_unlink,  security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_rename,  security_backup_ops);
    unreg_hook_func(security_custom_ops, inode_symlink, security_backup_ops);

    unreg_hook_func(security_custom_ops, bprm_check_security, security_backup_ops);
    unreg_hook_func(security_custom_ops, file_permission, security_backup_ops);
    unreg_hook_func(security_custom_ops, mmap_file, security_backup_ops);
    
    setback_cr0(cr0);
    
    return;
}

#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,2,0)

#include <linux/lsm_hooks.h>
#include <linux/rculist.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <linux/dcache.h>
#include <linux/security.h>
#include <linux/types.h>
#define HOOK_ARRAY_LENGTH    11

struct security_hook_heads *ak_security_hook_heads = NULL;
struct security_hook_list ak_hooks[HOOK_ARRAY_LENGTH]={};

#define AKLSM_HOOK_INIT(hookname,hookfunc,loop) \
    do{ \
        ak_hooks[loop].head = &(ak_security_hook_heads->hookname); \
        ak_hooks[loop].hook.hookname = hookfunc; \
    }while(0)
#if 0
static struct security_hook_list ak_hooks[] = {
    AKLSM_HOOK_INIT(inode_permission, hook_inode_permission),
    AKLSM_HOOK_INIT(inode_create, hook_inode_create),
    AKLSM_HOOK_INIT(inode_mkdir, hook_inode_mkdir),
    AKLSM_HOOK_INIT(inode_rmdir, hook_inode_rmdir),
    AKLSM_HOOK_INIT(inode_link, hook_inode_link),
    AKLSM_HOOK_INIT(inode_unlink, hook_inode_unlink),
    AKLSM_HOOK_INIT(inode_rename, hook_inode_rename),
    AKLSM_HOOK_INIT(inode_symlink, hook_inode_symlink),

    AKLSM_HOOK_INIT(bprm_check_security, hook_bprm_check_security),
    AKLSM_HOOK_INIT(file_permission, hook_file_permission),
    AKLSM_HOOK_INIT(mmap_file, hook_mmap_file),
};
#endif
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,11,0) && LINUX_VERSION_CODE < KERNEL_VERSION(4,17,0)
void custom_security_add_hooks(struct security_hook_list *hooks, int count, char *lsm)
{
	int i;

	for (i = 0; i < count; i++) {
		hooks[i].lsm = lsm;
		list_add_tail_rcu(&hooks[i].list, hooks[i].head);
	}
	return;
}
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4,17,0)
void custom_security_add_hooks(struct security_hook_list *hooks, int count, char *lsm)
{
	int i;
    unsigned int cr0;
    
    cr0 = clear_and_return_cr0();

	for (i = 0; i < count; i++) {
		hooks[i].lsm = lsm;
		hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
	}
	setback_cr0(cr0);
	return;
}
#endif

int custom_security_register(void)
{
    #if 0
    ak_security_hook_heads = (struct security_hook_heads *)find_symbol_addr("security_hook_heads");
    if(NULL == ak_security_hook_heads)
    {
        printkinfo(DBG_ERROR,"Read symbol security_hook_heads error\n");
        return -1;
    }
    #endif
    struct kernsym sym={0};

    (void)find_symbol_address(&sym,"security_hook_heads");
    ak_security_hook_heads = (void*)(long)sym.addr;
    if(NULL == ak_security_hook_heads)
    {
        printk("find symbol security_hook_heads error\n");
        return -1;
    }

    AKLSM_HOOK_INIT(inode_permission, hook_inode_permission,0);
    AKLSM_HOOK_INIT(inode_create, hook_inode_create,1);
    AKLSM_HOOK_INIT(inode_mkdir, hook_inode_mkdir,2);
    AKLSM_HOOK_INIT(inode_rmdir, hook_inode_rmdir,3);
    AKLSM_HOOK_INIT(inode_link, hook_inode_link,4);
    AKLSM_HOOK_INIT(inode_unlink, hook_inode_unlink,5);
    AKLSM_HOOK_INIT(inode_rename, hook_inode_rename,6);
    AKLSM_HOOK_INIT(inode_symlink, hook_inode_symlink,7);

    AKLSM_HOOK_INIT(bprm_check_security, hook_bprm_check_security,8);
    AKLSM_HOOK_INIT(file_permission, hook_file_permission,9);
    AKLSM_HOOK_INIT(mmap_file, hook_mmap_file,10);

    
    #if LINUX_VERSION_CODE < KERNEL_VERSION(4,11,0)
    security_add_hooks(ak_hooks, ARRAY_SIZE(ak_hooks));
    #else
    custom_security_add_hooks(ak_hooks, ARRAY_SIZE(ak_hooks),"akhooks");
    #endif

    return 0;
}
void custom_security_unregister(void)
{
    security_delete_hooks(ak_hooks, ARRAY_SIZE(ak_hooks));
    return;
}

#endif


#else


#endif
